﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WcfGeebaServer.ContentManage;
using WcfGeebaServer.Logging;
using WcfGeebaServer.Policies;
using WcfGeebaServer.Users;
using WcfGeebaServer.Utils;
using System.ServiceModel;


namespace WcfGeebaServer
{
    public class ServiceCore : IAppService
    {

        #region DataMembers

        protected GForum forum;
        //private static Dictionary<String,IListener> subscribers;// = new Dictionary<String,IListener>();
        protected static List<SubscribersType> subscribers = new List<SubscribersType>();

        #endregion

        public ServiceCore()
        {
            forum = GForum.GetForum();
        }

        public bool init(string admin, string adminPass, GPolicy policy)
        {
            return forum.init(new Member(admin, adminPass, true), policy);
        }

        #region GForum Methods

        public User entry()
        {
            try
            {
                return forum.entry();
            }
            catch (Exception e)
            {
                GLogger.error("Entry", e);
                return null;
            }
        }

        public bool register(string username, string pass)
        {
            try 
            {
                return forum.register(username, pass);
            }
            catch (Exception e)
            {
                GLogger.error("register", e);
                return false;
            }
        }

        public Member login(User oldUser, string username, string pass)
        {
            try 
            {
                return forum.login(oldUser, username, pass);
            }
            catch (Exception e)
            {
                GLogger.error("login", e);
                return null;
            }
        }

        public User logout(string memberUsername)
        {
            try 
            {
                if (!forum.IsMember(memberUsername))
                {
                    throw new Exception("Must be a member to logout");
                }
                return forum.logout(forum.GetMember(memberUsername));
            }
            catch (Exception e)
            {
                GLogger.error("logout", e);
                return null;
            }
        }

        public List<GSubForum> getSubForums()
        {
            try 
            {
                return forum.getSubForums();
            }
            catch (Exception e)
            {
                GLogger.error("getSubForums", e);
                return null;
            }
        }

        public List<GThread> getThreads(GSubForum subforum)
        {
            try
            {
                return forum.getThreads(subforum);
            }
            catch (Exception e)
            {
                GLogger.error("getThreads", e);
                return null;
            }
        }

        public bool addSubForum(Member requesting, string topic, string moderatorUsername)
        {
            try
            {
                if (requesting.UserType != UserType.Admin)
                {
                    throw new Exception("Only administrator can add a Sub Forum");
                }
                if (!forum.IsMember(moderatorUsername))
                {
                    throw new Exception("Moderator must be a member of the forum");
                }
                return forum.addSubForum(topic, forum.GetMember(moderatorUsername));
            }
            catch (Exception e)
            {
                GLogger.error("addSubForum", e);
                return false;
            }
        }

        public bool removeSubForum(Member requesting, string topic)
        {
            try
            {
                if (requesting.UserType != UserType.Admin)
                {
                    throw new Exception("Only administrator can remove a Sub Forum");
                }
                return forum.removeSubForum(topic);
            }
            catch (Exception e)
            {
                GLogger.error("removeSubForum", e);
                return false;
            }
        }

        public int getMemberThreadCount(Member requesting, string memberUsername)
        {
            try
            {
                if (requesting.UserType != UserType.Admin)
                {
                    throw new Exception("Only administrator can get the thread count");
                }
                return forum.GetThreadCount(memberUsername);
            }
            catch (Exception e)
            {
                GLogger.error("getMemberThreadCount", e);
                return -1;
            }
        }

        public bool changePassword(Member requesting, string oldPass, string newPass)
        {
            try
            {
                if (!forum.IsMember(requesting.Username))
                {
                    throw new Exception("Must be a member to change password");
                }
                return forum.changeMemberPassword(requesting.Username, oldPass, newPass);
            }
            catch (Exception e)
            {
                GLogger.error("changePassword", e);
                return false;
            }
        }

        public int getConnectedMembersCount()
        {
            try
            {
                return forum.getConnectedMembersCount();
            }
            catch (Exception e)
            {
                GLogger.error("getConnectedMembersCount", e);
                return -1;
            }
        }

        public bool SetActiveMember(Member requesting, bool active)
        {
            try
            {
                if (requesting == null)
                {
                    return false;
                }

                Member m = null;
                m = forum.GetMember(requesting.Username);
                if (m == null)
                {
                    // Not logged in
                    return false;
                }

                return m.setIsActive(active);
            }
            catch (Exception e)
            {
                GLogger.error("SetActiveMember", e);
                return false;
            }
        }

        public bool BanMember(Member requesting, string usernameToBan)
        {
            try
            {
                if (requesting == null)
                {
                    return false;
                }

                Member m = null;
                m = forum.GetMember(usernameToBan);
                if (m == null)
                {
                    // Not logged in
                    return false;
                }
                if (forum.Policy.CanBanMember(requesting, m))
                {
                    return m.BanMember();
                }

                return false;
            }
            catch (Exception e)
            {
                GLogger.error("BanMember", e);
                return false;
            }
        }

        #endregion

        #region GSubForum Methods

        public int publish(Member requesting, string topic, GThread message)
        {
            int res = -1;
            try
            {
                if (!forum.IsMember(requesting.Username))
                {
                    throw new Exception("Must be a member to publish a message");
                }
                //subforum.
                res = forum.getSubForum(topic).publish(message);
                GThread msg = new GThread(res, message);

                notifyListeners(msg);       
            }
            catch (Exception e)
            {
                GLogger.error("publish", e);
                res = -1;
            }
            
            return res;
        }

        public bool removeThread(Member requesting, string topic, int threadId)
        {
            try 
            {
                if (!forum.IsMember(requesting.Username))
                {
                    throw new Exception("Must be a member to remove a message");
                }
                return forum.getSubForum(topic).RemoveThread(requesting, threadId);
            }
            catch (Exception e)
            {
                GLogger.error("removeThread", e);
                return false;
            }
        }

        public List<Member> getModerators(Member requesting, string topic)
        {
            try
            {
                List<Member> res = new List<Member>();
                if (requesting.UserType == UserType.Guest)
                {
                    //throw new Exception("Only administrator can add a moderator");
                    return res;
                }

                foreach (KeyValuePair<Member, ModeratorInfo> mm in forum.getSubForum(topic).getModerators())
                {
                    res.Add(mm.Key);
                }

                return res;
            }
            catch (Exception e)
            {
                GLogger.error("getModerators", e);
                return null;
            }
        }

        public bool addModerator(Member requesting, string topic, string memberUsername)
        {
            try 
            {
                if (requesting.UserType != UserType.Admin)
                {
                    throw new Exception("Only administrator can add a moderator");
                }
                if (!forum.IsMember(memberUsername))
                {
                    throw new Exception("Moderator must be a member of the forum");
                }
                return forum.getSubForum(topic).addMemberAsModerator(forum.GetMember(memberUsername));
            }
            catch (Exception e)
            {
                GLogger.error("addModerator", e);
                return false;
            }
        }

        public bool replaceModerator(Member requesting, string topic, string oldUsername, string newUsername)
        {
            try 
            {
                if (requesting.UserType != UserType.Admin)
                {
                    throw new Exception("Only administrator can replace a moderator");
                }
                if (!forum.IsMember(oldUsername) || !forum.IsMember(newUsername))
                {
                   throw new Exception("The given Moderators are not members of the forum");
                }
                return forum.getSubForum(topic).ReplaceModerator(forum.GetMember(oldUsername), forum.GetMember(newUsername));
            }
            catch (Exception e)
            {
                GLogger.error("replaceModerator", e);
                return false;
            }
        }

        public bool removeModerator(Member requesting, string topic, string memberUsername)
        {
            try 
            {
                if (requesting.UserType != UserType.Admin)
                {
                    throw new Exception("Only administrator can remove a moderator");
                }
                if (!forum.IsMember(memberUsername))
                {
                    throw new Exception("The given Moderator is not a member of the forum");
                }
                return forum.getSubForum(topic).removeMemberFromModerators(forum.GetMember(memberUsername));
            }
            catch (Exception e)
            {
                GLogger.error("removeModerator", e);
                return false;
            }
        }

        public int getThreadCount(Member requesting, string topic)
        {
            try 
            {
                if (requesting.UserType != UserType.Admin)
                {
                    throw new Exception("Only administrator can get the thread count");
                }
                return forum.getSubForum(topic).GetThreadCount();
            }
            catch (Exception e)
            {
                GLogger.error("getThreadCount", e);
                return -1;
            }
        }

        #endregion

        #region GThread Methods

        public int comment(Member requesting, GPost message, string topic, int threadId)
        {
            try 
            {
                if (!forum.IsMember(requesting.Username))
                {
                    throw new Exception("Must be a member to comment on a thread");
                }
                GSubForum sf = forum.getSubForum(topic);
                if (sf != null)
                {
                    GThread th = sf.GetThread(threadId);
                    if (th != null){
                        int res = th.addPost(message);
                        GPost msg = th.GetPost(res);
                        notifyListeners(msg);
                        return res;
                    }
                }
                return -1;
            }
            catch (Exception e)
            {
                GLogger.error("comment", e);
                return -1;
            }
        }

        public List<GPost> getComments(string topic, int threadId)
        {
            try 
            {
                GSubForum sf = forum.getSubForum(topic);
                if (sf != null)
                {
                    GThread th = sf.GetThread(threadId);
                    if (th != null)
                    {
                        return th.getPosts();
                    }
                }
                return null;
            }
            catch (Exception e)
            {
                GLogger.error("getComments", e);
                return null;
            }
        }
        
        public bool editContent(Member requesting, string topic, int threadId, string newContent)
        {
            try 
            {
                if (!forum.IsMember(requesting.Username))
                {
                    throw new Exception("Must be a member to edit a thread");
                }
                GThread thread = forum.getSubForum(topic).GetThread(threadId);
                if (thread.publishedBy.Username != requesting.Username && requesting.UserType != UserType.Admin)
                {
                    throw new Exception("Only publishing member can edit the message");
                }
                bool res = forum.getSubForum(topic).GetThread(threadId).editContent(requesting, newContent);
                notifyListenersMessageEdited(topic, forum.getSubForum(topic).GetThread(threadId));
                return res;
            }
            catch (Exception e)
            {
                GLogger.error("editContent", e);
                return false;
            }
        }

        public bool bumpThread(Member requesting, string topic, int threadId)
        {
            try 
            {
                if (!forum.IsMember(requesting.Username))
                {
                    throw new Exception("Must be a member to edit a thread");
                }
                return forum.getSubForum(topic).GetThread(threadId).bump();
            }
            catch (Exception e)
            {
                GLogger.error("bumpThread", e);
                return false;
            }
        }

        public bool replaceAdmin(Member requesting, string newAdmin)
        {
            try
            {
                if (forum.Admin.Username != requesting.Username)
                {
                    throw new Exception("Must be admin to replace admin");

                }
                return forum.replaceAdmin(forum.GetMember(newAdmin));
            }
            catch (Exception e)
            {
                GLogger.error("replaceAdmin", e);
                return false;
            }
        }

        #endregion

        #region interactive Methods

        public virtual bool subscribe(string username)
        {
            try
            {
                IListener listener = OperationContext.Current.GetCallbackChannel<IListener>();
                String ID = OperationContext.Current.SessionId;

                SubscribersType group = new SubscribersType { Username = username, SessionId = ID, Listener = listener };
                subscribers.RemoveAll((x) => x.Listener == listener && x.SessionId == ID);

                if(forum.Policy.CanSubscribe(username))
                    subscribers.Add(group);
                return true;
            }
            catch (Exception e)
            {
                GLogger.error("subscribe", e);
                return false;
            }
        }

        public virtual bool unsubscribe()
        {
            try
            {
                IListener listener = OperationContext.Current.GetCallbackChannel<IListener>();
                String ID = OperationContext.Current.SessionId;

                subscribers.RemoveAll((x) => x.SessionId == ID);
                return true;
            }
            catch (Exception e)
            {
                GLogger.error("unsubscribe", e);
                return false;
            }
        }

        protected void notifyListeners(GThread message)
        {
            try
            {
                foreach (SubscribersType listenterEntry in subscribers)
                {
                    if (((ICommunicationObject)listenterEntry.Listener) != null) // WPF - Console Listener
                    {
                        if (forum.Policy.CanSubscribe(listenterEntry.Username))
                        {
                            if (((ICommunicationObject)listenterEntry.Listener).State == CommunicationState.Opened)
                            {
                                Member member = forum.GetMember(listenterEntry.Username);
                                bool isNotification = forum.Policy.IsNotification(member, message);
                                listenterEntry.Listener.onUpdate(message, DateTime.Now, isNotification);
                            }
                            else
                            {
                                subscribers.Remove(listenterEntry);
                            }
                        }
                        else
                        {
                            subscribers.RemoveAll((x) => x.Username == listenterEntry.Username);
                        }
                    }
                    else if (((ICommunicationObject)listenterEntry.SListener) != null)
                    {
                        // SilverLight Listener
                        if (forum.Policy.CanSubscribe(listenterEntry.Username))
                        {
                            if (((ICommunicationObject)listenterEntry.SListener).State == CommunicationState.Opened)
                            {
                                Member member = forum.GetMember(listenterEntry.Username);
                                bool isNotification = forum.Policy.IsNotification(member, message);
                                try
                                {
                                    listenterEntry.SListener.BeginUpdate(message, DateTime.Now, isNotification, _UpdateCompleted, listenterEntry.SListener);
                                }
                                catch (TimeoutException texp)
                                {
                                    GLogger.error("notifyListeners timeout", texp);
                                }
                                catch (CommunicationException cexp)
                                {
                                    GLogger.error("notifyListeners communication", cexp);
                                }
                            }
                            else
                            {
                                subscribers.Remove(listenterEntry);
                            }
                        }
                        else
                        {
                            subscribers.RemoveAll((x) => x.Username == listenterEntry.Username);
                        }

                    }
                }
            }
            catch (Exception e)
            {
                GLogger.error("notifyListeners", e);
            }
        }

        protected void notifyListenersMessageEdited(string subforumTopic, GThread message)
        {
            try
            {
                foreach (SubscribersType listenterEntry in subscribers)
                {
                    if (((ICommunicationObject)listenterEntry.Listener) != null)
                    {
                        // WPF - Console Listener
                        if (((ICommunicationObject)listenterEntry.Listener).State == CommunicationState.Opened)
                        {
                            listenterEntry.Listener.onEditMessage(subforumTopic, message, DateTime.Now);
                        }
                        else
                        {
                            subscribers.Remove(listenterEntry);
                        }
                    }
                    else if (((ICommunicationObject)listenterEntry.SListener) != null)
                    {
                        // SilverLight Listener
                        if (((ICommunicationObject)listenterEntry.SListener).State == CommunicationState.Opened)
                        {
                            try
                            {
                                listenterEntry.SListener.BeginEditMessage(subforumTopic, message, DateTime.Now, _EditCompleted, listenterEntry.SListener);
                            }
                            catch (TimeoutException texp)
                            {
                                GLogger.error("notifyListeners timeout", texp);
                            }
                            catch (CommunicationException cexp)
                            {
                                GLogger.error("notifyListeners communication", cexp);
                            }
                        }
                        else
                        {
                            subscribers.Remove(listenterEntry);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                GLogger.error("notifyListenersMessageEdited", e);
            }
        }

        //silver light interactive help methods
        static AsyncCallback _UpdateCompleted = new AsyncCallback(UpdateCompleted);
        static AsyncCallback _EditCompleted = new AsyncCallback(EditCompleted);

        private static void EditCompleted(IAsyncResult result)
        {
            try
            {
                ((ISListener)(result.AsyncState)).EndEditMessage(result);
            }
            catch (CommunicationException)
            {
                // empty
            }
            catch (TimeoutException)
            {
                // empty
            }
        }
        private static void UpdateCompleted(IAsyncResult result)
        {
            try
            {
                ((ISListener)(result.AsyncState)).EndUpdate(result);
            }
            catch (CommunicationException)
            {
                // empty
            }
            catch (TimeoutException)
            {
                // empty
            }
        }

        #endregion

        #region SearchMethods

        public List<GThread> searchByContent(string content)
        {
            try
            {
                return this.forum.searchByContent(content);
            }
            catch (Exception e)
            {
                GLogger.error("searchByContent", e);
                return null;
            }

        }

        public List<GThread> searchByUser(string username)
        {
            try
            {
                return this.forum.searchByUser(username);
            }
            catch (Exception e)
            {
                GLogger.error("searchByUser", e);
                return null;
            }
        }

        #endregion
    }
    
}